/*
 * Copyright (c) 2015-2018 SHENZHEN TOMTOP SCIENCE AND TECHNOLOGY DEVELOP CO., LTD. All rights reserved.
 *
 * 注意：本内容仅限于深圳市通拓科技研发有限公司内部传阅，禁止外泄以及用于其他的商业目的 
 */
package com.jaws.core.common.util;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;

import com.jaws.core.common.enums.SnowflakeModeEnum;

/**
 * 基础算法来源Twitter_Snowflake，
 * 本id生成算法取ipv4的地址作为worker_index，regin_index默认为0(适用于中型系统，不考虑跨机房情况)，
 * 方便自动化部署 ,以ip地址做workerid好处，最大为255 + 255 + 255 + 255 =1020 未超出1024 ，方便使用<br>
 * 本算法不适用于主机ip地址是乱序情况，例如：一个机器ip地址为 255.244.255.255 另一个 为255.255.244.255
 * ip地址累加值相同，如果出现是同一个服务就会引起主键冲突问题；要求一定要保障尽量集群主机在同一个网段或者某个服务都在同一网段才可使用
 * SnowFlake的结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * 1位标识，由于long基本类型在Java中是带符号的，最高位是符号位，正数是0，负数是1，所以id一般是正数，最高位是0<br>
 * 41位时间截(毫秒级)，注意，41位时间截不是存储当前时间的时间截，而是存储时间截的差值（当前时间截 - 开始时间截)
 * 得到的值），这里的的开始时间截，一般是我们的id生成器开始使用的时间，由我们程序来指定的（如下下面程序IdWorker类的startTime属性）。41位的时间截，可以使用69年，年T = (1L << 41) / (1000L * 60 * 60 * 24 *
 * 365) = 69<br>
 * 10位的数据机器位，可以部署在1024个节点，包括5位datacenterId和5位workerId<br>
 * 12位序列，毫秒内的计数，12位的计数顺序号支持每个节点每毫秒(同一机器，同一时间截)产生4096个ID序号<br>
 * 加起来刚好64位，为一个Long型。<br>
 * 
 * @author lixin
 * @date 2018-03-06 09:02:24
 * @since TODO(说明当前修改版本号)
 */
public class IPKeyGeneratorUtil {
	// 基准时间
	private static long twepoch = 1288834974657L; // Thu, 04 Nov 2010 01:42:54 GMT
	// 区域标志位数
	private final static long regionIdBits = 3L;
	// 机器标识位数
	private final static long workerIdBits = 10L;
	// 序列号识位数
	private final static long sequenceBits = 10L;

	// 区域标志ID最大值
	private final static long maxRegionId = -1L ^ (-1L << regionIdBits);
	// 机器ID最大值
	private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
	// 序列号ID最大值
	private final static long sequenceMask = -1L ^ (-1L << sequenceBits);

	// 机器ID偏左移10位
	private final static long workerIdShift = sequenceBits;
	// 业务ID偏左移20位
	private final static long regionIdShift = sequenceBits + workerIdBits;
	// 时间毫秒左移23位
	private final static long timestampLeftShift = sequenceBits + workerIdBits + regionIdBits;

	private static long lastTimestamp = -1L;

	private static long sequence = 0L;

	/**
	 * 第几个机器，数值不可用超过1023，从0开始，最多可以扩到第1024个机器
	 */
	private static long workerId = 0L;

	/**
	 * 第几个机房 ，数值不可用超过7，从0开始，最多可以扩到8个机房
	 */
	private static long datacenterId = 0L;


	private SnowflakeModeEnum mode = SnowflakeModeEnum.HOST;


	public IPKeyGeneratorUtil() {
		switch (mode) {
		case HOST:
			workerId = generateWorkerIdByHost();
			break;
		case IPV4:
			workerId = generateWorkerIdByIpv4();
			break;
		default:
			break;
		}
	}


	public static long generate() {
		return nextId(false, 0);
	}

	/**
	 * 实际产生代码的
	 *
	 * @param isPadding
	 * @param busId
	 * @return
	 */
	private static synchronized long nextId(boolean isPadding, long busId) {

		long timestamp = timeGen();
		long paddingnum = datacenterId;

		if (isPadding) {
			paddingnum = busId;
		}

		if (timestamp < lastTimestamp) {

			long delay = lastTimestamp - timestamp;

			// 如果时间偏差较小则等待
			if (delay < 5) {

				try {
					Thread.sleep(delay);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				timestamp = timeGen();
			}

			// 如果还没好则抛异常(后续加报警代码实现)
			if (timestamp < lastTimestamp) {
				try {
					throw new Exception("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
				} catch (Exception e) {
					e.printStackTrace();
				}
			} else {
				return generate();
			}

		}

		// 如果上次生成时间和当前时间相同,在同一毫秒内
		if (lastTimestamp == timestamp) {
			// sequence自增，因为sequence只有10bit，所以和sequenceMask相与一下，去掉高位
			sequence = (sequence + 1) & sequenceMask;
			// 判断是否溢出,也就是每毫秒内超过1024，当为1024时，与sequenceMask相与，sequence就等于0
			if (sequence == 0) {
				// 自旋等待到下一毫秒
				timestamp = tailNextMillis(lastTimestamp);
			}
		} else {
			// 如果和上次生成时间不同,重置sequence，就是下一毫秒开始，sequence计数重新从0开始累加,
			// 为了保证尾数随机性更大一些,最后一位设置一个随机数
			sequence = new SecureRandom().nextInt(10);
		}

		lastTimestamp = timestamp;

		return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence;
	}

	// 防止产生的时间比之前的时间还要小（由于NTP回拨等问题）,保持增量的趋势.
	private static long tailNextMillis(final long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}

	// 获取当前的时间戳
	protected static long timeGen() {
		return System.currentTimeMillis();
	}

	/**
	 * ip后两个网段计算，要求前面网段一致
	 * 
	 * @author maojian
	 * @date 2018-03-16 10:37:52
	 * @since v1.0.0
	 * @return
	 */
	private long generateWorkerIdByIpv4() {
		Long workerId;

		InetAddress address;

		try {
			// address = InetAddress.getByName("172.29.1.5");
			address = InetAddress.getLocalHost();
		} catch (final UnknownHostException e) {
			throw new IllegalStateException("Cannot get LocalHost InetAddress, please check your network!");
		}
		byte[] ipAddressByteArray = address.getAddress();

		workerId = (long) (((ipAddressByteArray[ipAddressByteArray.length - 2] & 0B11) << Byte.SIZE) + (ipAddressByteArray[ipAddressByteArray.length - 1] & 0xFF));

		return workerId;
	}

	/**
	 * 根据hostname，取“-”后的数字
	 * 
	 * @author maojian
	 * @date 2018-03-16 10:40:09
	 * @since v1.0.0
	 *
	 * @return
	 */
	private long generateWorkerIdByHost() {
		InetAddress address;
        Long workerId;
        try {
            address = InetAddress.getLocalHost();
        } catch (final UnknownHostException e) {
            throw new IllegalStateException("Cannot get LocalHost InetAddress, please check your network!");
        }
        String hostName = address.getHostName();
        try {
            workerId = Long.valueOf(hostName.substring(hostName.lastIndexOf("-")));
        } catch (final NumberFormatException e) {
            throw new IllegalArgumentException(String.format("Wrong hostname:%s, hostname must be end with number!", hostName));
        }
        
		return workerId;
	}

	public static void main(String args[]) {
		// SnowflakeUtil uid = new SnowflakeUtil(1023,7);
		// for (int i = 0; i < 1000; i++) {
		// System.out.println(IPKeyGeneratorUtil.generate());
		// }
		System.out.println(IPKeyGeneratorUtil.generate());
	}
}
